﻿using Microsoft.SharePoint.Client;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace CloudStorageLight.Core
{
    public class SpsBlobAdapter : BlobAdapterBase
    {
        protected string SpSiteUrl { get; set; }
        string user;
        string password;

        internal string documentLibraryName;
        internal string rootUriPath;
        protected virtual ClientContext SpsClientContext
        {
            get
            {
                if (_SpsClientContext == null)
                {
                    var securePassword = new SecureString();
                    foreach (char c in password)
                    {
                        securePassword.AppendChar(c);
                    }

                    var onlineCredentials = new SharePointOnlineCredentials(user, securePassword);

                    _SpsClientContext = new ClientContext(SpSiteUrl);
                    _SpsClientContext.Credentials = onlineCredentials;
                }
                return _SpsClientContext;
            }
        }
        private ClientContext _SpsClientContext;

        public SpsBlobAdapter(string rootPath, string spSiteUrl, string user, string password, string documentLibraryName, string timezone=null)
        {
            this.SpSiteUrl = spSiteUrl;
            this.documentLibraryName = documentLibraryName;
            this.rootUriPath = spSiteUrl.TrimEnd('/') + "/" + documentLibraryName + "/";
            this.user = user;
            this.password = password;

            this.RootPath = rootPath;

            this.TimeZone = string.IsNullOrEmpty(timezone) ? "UTC" : timezone;
        }

        public string ConvertSharePointPathToUrlPath(string spoPath)
        {
            var docFolderPath = new Uri(SpSiteUrl).AbsolutePath.TrimEnd('/') + "/" + this.documentLibraryName + "/";
            if (docFolderPath.Length > spoPath.Length) return RootPath;
            var uriPath = spoPath.Substring(docFolderPath.Length);
            uriPath = RootPath + uriPath;
            return uriPath;
        }

        public string ConvertUrlPathToSharePointPath(string urlPath)
        {
            var docFolderPath = new Uri(SpSiteUrl).AbsolutePath.TrimEnd('/')+  "/" + this.documentLibraryName + "/";
            return docFolderPath + urlPath.Substring(RootPath.Length);
        }




        private IEnumerable<BlobItem> ListFileBlob(ClientContext spCtx, Folder folder, bool recursive = true)
        {

            if (folder != null)
            {
                spCtx.Load(folder, x => x.ServerRelativeUrl);
                spCtx.ExecuteQuery();

                var path = ConvertSharePointPathToUrlPath(folder.ServerRelativeUrl);
                yield return new BlobDirectory(Parent, path);

                var resultsFiles = spCtx.LoadQuery(folder.Files.Include(x => x.Name, x => x.ServerRelativeUrl, x => x.TimeLastModified, x => x.Length, x => x.ModifiedBy, x=>x.CheckOutType, x=>x.CheckedOutByUser.Email));
                spCtx.ExecuteQuery();
                foreach (var item in resultsFiles)
                {
                    var logicalPath = ConvertSharePointPathToUrlPath(item.ServerRelativeUrl);
                    var retItem = new BlobFile(Parent, logicalPath) { LastModified = ConvertTimezone(item.TimeLastModified), Length = item.Length, Owner = item.ModifiedBy.Email };
                    if (item.CheckOutType != CheckOutType.None)
                    {
                        retItem.CheckOutUserId = Messages.MsgOtherUser;
                    }
                    else
                    {
                        retItem.CheckOutUserId = null;
                    }
                    yield return retItem;
                }
                var resultsFolder = spCtx.LoadQuery(folder.Folders.Include(x => x.Name, x => x.ServerRelativeUrl, x => x.TimeLastModified));
                spCtx.ExecuteQuery();
                foreach (var item in resultsFolder)
                {
                    if (recursive)
                    {
                        foreach (var subItem in ListFileBlob(spCtx, item, recursive))
                        {
                            yield return subItem;
                        }
                    }
                    else
                    {
                        var subPath = ConvertSharePointPathToUrlPath(item.ServerRelativeUrl);
                        if (subPath == RootPath + "Forms") continue;
                        yield return new BlobDirectory(Parent, subPath);
                    }
                }
            }
        }

        private Folder GetFolder(ClientContext spCtx, string folder=null)
        {
            var folderPath = new Uri(SpSiteUrl).AbsolutePath.TrimEnd('/') + "/" + this.documentLibraryName + "/";
            var docLib = spCtx.Web.GetFolderByServerRelativeUrl(folder ?? folderPath);
            spCtx.Load(docLib, x => x.Folders, x => x.Files, x => x.Name, x=>x.ServerRelativeUrl);
            spCtx.ExecuteQuery();
            return docLib;

        }


        /// <summary>
        /// 指定したファイルが存在するか
        /// </summary>
        /// <param name="path2"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        protected override async Task<bool> ExistsAsyncInternal(string path)
        {
            var spsPath =ConvertUrlPathToSharePointPath(path);
            if (spsPath == "/") return true;
            if (spsPath.EndsWith("/"))
            {
                try
                {
                    var web = SpsClientContext.Web;
                    SpsClientContext.Load(web, x=>x.Url, x=>x.Lists);
                    SpsClientContext.ExecuteQuery();
                    var spsDirectory = web.GetFolderByServerRelativeUrl(spsPath);
                    SpsClientContext.Load(spsDirectory);
                    SpsClientContext.ExecuteQuery();
                    SpsClientContext.Load(spsDirectory, x => x.TimeLastModified);
                    SpsClientContext.ExecuteQuery();
                    return true;
                }
                catch (ServerException ex)
                {
                    if (ex.ServerErrorTypeName != "System.IO.FileNotFoundException")
                    {
                        throw;
                    }
                    return false;
                }
            }
            else
            {
                try
                {
                    var file = SpsClientContext.Web.GetFileByServerRelativeUrl(spsPath);
                    SpsClientContext.Load(file, x => x.Exists);
                    SpsClientContext.ExecuteQuery();
                    var ret = file.Exists;
                    return ret;
                }
                catch (ServerException ex)
                {
                    if (ex.ServerErrorTypeName != "System.IO.FileNotFoundException")
                    {
                        throw;
                    }
                    return false;
                }
            }
        }
        #pragma warning restore 1998

        /// <summary>
        /// ディレクトリののファイル一覧を取得する
        /// </summary>
        /// <param name="id"></param>
        /// <param name="maxSize"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task<List<BlobItem>> GetFilesAsync(string id, int maxSize, bool flat = false)
        {
            var docLib = GetFolder(SpsClientContext, ConvertUrlPathToSharePointPath(id));
            var result = ListFileBlob(SpsClientContext, docLib, flat);
            if (maxSize > 0) return result.OrderBy(x=>x.Path).Take(maxSize).ToList();
            return result.ToList();
        }
        #pragma warning restore 1998

        /// <summary>
        /// Blobのコピー
        /// </summary>
        /// <param name="path"></param>
        /// <param name="fromUri"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task CopyAsync(string path, BlobFile file)
        {
            var spoPathDest = ConvertUrlPathToSharePointPath(path);
            var spoPathSrc = ConvertUrlPathToSharePointPath(file.Path);
            var srcFile = SpsClientContext.Web.GetFileByServerRelativeUrl(spoPathSrc);
            var docLib = GetFolder(SpsClientContext);
            CreateDirectory(SpsClientContext, docLib, GetParentFolder(spoPathDest));
            srcFile.CopyTo(spoPathDest, true);
            SpsClientContext.ExecuteQuery();
        }
        #pragma warning restore 1998

        void CreateDirectory(ClientContext spCtx, Folder folder, string subDirectory)
        {
            if (!subDirectory.StartsWith(folder.ServerRelativeUrl)) throw new ArgumentException("subDirectory");
            subDirectory = subDirectory.Substring(folder.ServerRelativeUrl.Length);
            foreach (var item in subDirectory.Split('/'))
            {
                if (string.IsNullOrEmpty(item)) continue;
                var work = folder.Folders.FirstOrDefault(x => x.ServerRelativeUrl == folder.ServerRelativeUrl + "/" + item);
                if (work != null)
                {
                    folder = work;
                    spCtx.Load(folder.Folders, x=>x.Include(y=>y.ServerRelativeUrl));
                    spCtx.ExecuteQuery();
                }
                else
                {
                    folder = folder.Folders.Add(item);
                }
            }
            spCtx.ExecuteQuery();
        }

        string GetParentFolder(string path)
        {
            return path.Substring(0, path.Length - path.Split('/').Last().Length);
        }

        /// <summary>
        /// 削除
        /// </summary>
        /// <returns></returns>
        public override async Task<bool> DeleteAsync(BlobItem file)
        {
            if (await ExistsAsync(file.Path))
            {
                var path = ConvertUrlPathToSharePointPath(file.Path);
                if (file is BlobDirectory)
                {
                    var direItem = SpsClientContext.Web.GetFolderByServerRelativeUrl(path);
                    direItem.Recycle();
                }
                else
                {
                    var fileItem = SpsClientContext.Web.GetFileByServerRelativeUrl(path);
                    fileItem.Recycle();
                }
                SpsClientContext.ExecuteQuery();
                ClearExistsCahce();

                return true;
            }
            else
            {
                ClearExistsCahce();
                return false;
            }

        }

 


        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task UploadAsync(BlobItem item, Stream stream)
        {

            var path = ConvertUrlPathToSharePointPath(item.Path);


            FileCreationInformation flciNewFile = new FileCreationInformation();

            flciNewFile.ContentStream = stream;
            flciNewFile.Url = item.Name;
            flciNewFile.Overwrite = true;

            var direItem = SpsClientContext.Web.GetFolderByServerRelativeUrl(GetParentFolder(path));
            var uploadFile = direItem.Files.Add(flciNewFile);

            SpsClientContext.Load(uploadFile);
            if (item.LastWriteTime.HasValue) uploadFile.ListItemAllFields["Modified"] = item.LastWriteTime.Value.ToString("O");
            if (item.CreationTime.HasValue) uploadFile.ListItemAllFields["Created"] = item.CreationTime.Value.ToString("O");
            if (item.LastWriteTime.HasValue || item.CreationTime.HasValue) uploadFile.ListItemAllFields.Update();

            SpsClientContext.ExecuteQuery();
            ClearExistsCahce();
        }
        #pragma warning restore 1998

        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        public override async Task UploadAsync(BlobItem item, string localpath)
        {
            using (var st = new FileStream(localpath, FileMode.Open))
            {
                await UploadAsync(item, st);
            }
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, Stream stream)
        {
            var path = ConvertUrlPathToSharePointPath(item.Path);
            var file = SpsClientContext.Web.GetFileByServerRelativeUrl(path);

            //必要な意味を確認
            //var docLib = GetFolder(SpsClientContext);
            //CreateDirectory(SpsClientContext, docLib, GetParentFolder(path));

            var data = file.OpenBinaryStream();
            SpsClientContext.ExecuteQuery();
            await data.Value.CopyToAsync(stream);
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, string localpath)
        {
            var spoPath = ConvertUrlPathToSharePointPath(item.Path);

            using (var st = new FileStream(localpath, FileMode.Create))
            {
                await DownloadAsync(item, st);
            }
        }


        /// <summary>
        /// フォルダ作成
        /// </summary>
        /// <param name="dire"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task CreateDirectoryAsync(BlobDirectory dire)
        {
            var spoPath = ConvertUrlPathToSharePointPath(dire.Path);
            var docLib = GetFolder(SpsClientContext);
            CreateDirectory(SpsClientContext, docLib, GetParentFolder(spoPath));
            ClearExistsCahce();
        }
        #pragma warning restore 1998

        /// <summary>
        /// メタ情報の設定
        /// </summary>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task SetMetadataAsync(BlobItem file)
        {
            file.Link = ConvertUrlPathToSharePointPath(file.Path);
        }
        #pragma warning restore 1998

        /// <summary>
        /// メタ情報の取得
        /// </summary>
        /// <param name="fetch"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task GetMetadataAsync(BlobItem file)
        {

            var path = ConvertUrlPathToSharePointPath(file.Path);

            try
            {
                if (file.Path.EndsWith("/"))
                {
                    var spsDirectory = SpsClientContext.Web.GetFolderByServerRelativeUrl(path);
                    SpsClientContext.Load(spsDirectory, x => x.TimeLastModified);
                    SpsClientContext.ExecuteQuery();

                    file.LastModified = ConvertTimezone(spsDirectory.TimeLastModified);
                }
                else
                {
                    var f = (BlobFile)file;
                    var spsFile = SpsClientContext.Web.GetFileByServerRelativeUrl(path);
                    if (spsFile != null)
                    {
                        SpsClientContext.Load(spsFile, x => x.TimeLastModified, x => x.Length, x => x.ModifiedBy.Email, x => x.CheckedOutByUser.Email, x=>x.CheckOutType);
                        SpsClientContext.ExecuteQuery();

                        if (spsFile.CheckOutType != CheckOutType.None)
                        {
                            if (!f.IsCheckOut) f.CheckOutUserId = Messages.MsgOtherUser;
                        }
                        else
                        {
                            f.CheckOutUserId = null;
                        }                       
                        f.Length = spsFile.Length;
                        f.LastModified = ConvertTimezone(spsFile.TimeLastModified);
                        f.Owner = spsFile.ModifiedBy.Email;
                    }
                }
            }
            catch (ServerException ex)
            {
                if (ex.ServerErrorTypeName != "System.IO.FileNotFoundException")
                {
                    throw;
                }
            }
        }
        #pragma warning restore 1998

        public override Task<bool> CheckInActionAsync(BlobUser user, BlobFile file)
        {
            LogService.DefaultLogService.EntryLog(user.Name, "", "CheckInActionAsync", file.Path, "" );

            var path = ConvertUrlPathToSharePointPath(file.Path);
            var spsFile = SpsClientContext.Web.GetFileByServerRelativeUrl(path);
            spsFile.CheckIn("", CheckinType.OverwriteCheckIn);
            SpsClientContext.ExecuteQuery();
            return Task.FromResult(true);
        }

        public override Task<bool> CheckOutActionAsync(BlobUser user, BlobFile file)
        {
            LogService.DefaultLogService.EntryLog(user.Name, "", "CheckOutActionAsync", file.Path, "");

            var path = ConvertUrlPathToSharePointPath(file.Path);
            var spsFile = SpsClientContext.Web.GetFileByServerRelativeUrl(path);
            spsFile.CheckOut();
            SpsClientContext.ExecuteQuery();
            return Task.FromResult(true);
        }


        public override void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        bool disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (disposed) return;
            if (disposing)
            {
                SpsClientContext.Dispose();
            }

            disposed = true;
        }

        /// <summary>
        /// メタ情報を設定
        /// </summary>
        public override void MergeMetaValue(Dictionary<string, string> meta, string key, string value)
        {
            if (key == BlobFile.keyCheckoutuserid)
            {
                if (meta[key] == null && value != null) return;
                if (meta[key] != null && value == null) return;
            }
            meta[key] = value;
        }

    }
}
